﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DARE;
using Microsoft.Xna.Framework;

namespace DARE
{
    /// <summary>
    /// It represents an inventory. A widget that will contains Vignettes.
    /// You can set the total number of slots of the inventory and the number of slots by line.
    /// </summary>
    /// <example>
    /// DARE.CInventory inv = new DARE.CInventory(HUD, null, "inventory");
    /// inv.Position = new Vector2(450, 50);
    /// inv.NbItems = 20;
    /// inv.AllowedTypes.Add(2);
    /// inv.AllowedTypes.Add(1);
    /// inv.AllowedTypes.Add(5);
    /// </example>
    [AttrTypeDependentSerializable(typeof(CInventoryData))]
    public class CInventory : CWidget, IHudContainerGroup
    {
        #region identifier

        /// <summary>
        /// this delegate will be called when the inventory is full and we try to attach an item on it.
        /// Prototype for this delegate:
        /// bool fullInventoryEventHandler(CInventory sender, object data);
        /// </summary>
        /// <param name="sender">this will be the inventory that calls this delegate</param>
        /// <param name="data"></param>
        /// <returns>return true if you want the item to be destroyed</returns>
        public delegate bool fullInventoryEventHandler(CInventory sender, object data);

        #endregion

        #region fields

        private List<CContainerLine> m_lines;

        private Vector2 m_itemsSize;
        private float m_interval;
        private int m_nbByLine;
        private int m_nbItems;
        private List<long> m_allowedTypes;


        #endregion

        #region properties

        /// <summary>
        /// Represents the event invocated whenever the CContainerLine is full (every slots are filled).
        /// </summary>
        public event fullInventoryEventHandler FullInventoryEventHandler;

        /// <summary>
        /// If the value is between 0 and 1, the size is a percentage of the size of the parent
        /// (if the parent is null, the value is a percentage of the window).
        /// Else, the value is absolute.
        /// </summary>
        public override Vector2 Size
        {
            get
            {
                return base.Size;
            }
            set
            {
                return;
            }
        }

        /// <summary>
        /// If the value is between 0 and 1, the position is a percentage of the size of the parent
        /// (if the parent is null, the value is a percentage of the window).
        /// The value is relative to its parent.
        /// </summary>
        public override Vector2 Position
        {
            get
            {
                return base.Position;
            }
            set
            {
                base.Position = value;
            }
        }

        /// <summary>
        /// Gets/Sets the rectangle where the widget will be draw.
        /// This value is automatically set when the position and/or the size are set.
        /// If you plan to use a different rectangle than the one automatically created,
        /// we recommand you to use the UseDrawableRect and DrawableRect properties.
        /// </summary>
        /// <seealso cref="UseDrawableRect"/>
        /// <seealso cref="DrawableRect"/>
        public override Rectangle Rect
        {
            get
            {
                return base.Rect;
            }
            set
            {
                return;
            }
        }

        /// <summary>
        /// Gets/Sets the size of each slot in this inventory
        /// </summary>
        /// <summary>
        /// Gets/Sets the size of each slot in this inventory
        /// </summary>
        public Vector2 ItemsSize
        {
            get { return m_itemsSize; }
            set
            {
                m_itemsSize = value;
                if (m_itemsSize.X <= 0)
                    m_itemsSize.X = 1;
                if (m_itemsSize.Y <= 0)
                    m_itemsSize.Y = 1;
                GenerateWidget();
            }
        }

        /// <summary>
        /// Gets/Sets the interval between each slot in this inventory
        /// </summary>
        /// <summary>
        /// Gets/Sets the interval between each slot in this inventory
        /// </summary>
        public float Interval
        {
            get { return m_interval; }
            set
            {
                m_interval = value;
                if (m_interval < 0)
                    m_interval = 0;
                GenerateWidget();
            }
        }

        /// <summary>
        /// Gets/Sets the number of slots by line.
        /// Will automatically add lines relatively to the number of containers by line and of the number of elements.
        /// </summary>
        public int NbByLine
        {
            get { return m_nbByLine; }
            set
            {
                m_nbByLine = value;
                if (m_nbByLine <= 1)
                    m_nbByLine = 1;
                GenerateWidget();
            }
        }

        /// <summary>
        /// Gets/Sets the total number of disponible slots
        /// </summary>
        public int NbItems
        {
            get { return m_nbItems; }
            set
            {
                m_nbItems = value;
                if (m_nbItems < 1)
                    m_nbItems = 1;
                GenerateWidget();
            }
        }

        /// <summary>
        /// Defines every types of vignettes (CVignette.Type) that is allowed on this inventory.
        /// For example, if you use this inventory for skillbar and the vignette represents a equipment
        /// and the type of the vignette defines the equipment (as sword) type, the vignette will not be able to be
        /// attached on it.
        /// </summary>
        public List<long> AllowedTypes { get { return m_allowedTypes; } set { m_allowedTypes = value; foreach (CContainerLine line in m_lines) line.AllowedTypes = value; } }

        #endregion

        #region ctor

        public CInventory(CHUDSystem hud, CWidget parent, string name)
            :base(hud, parent, name)
        {
            IsContainerGroup = true;
            m_texture = hud.Skinner.GetTexture("defaultwidget");
            m_lines = new List<CContainerLine>();
            m_nbItems = 16;
            m_interval = 2;
            m_nbByLine = 5;
            m_itemsSize = Vector2.One * 40;
            m_allowedTypes = new List<long>();
            GenerateWidget();
        }


        #endregion

        #region clone

        public override CWidget Clone()
        {
            CWidget w = new CInventory(HUD, null, m_name + "_clone" + m_nbClones);
            ++m_nbClones;
            Clone(ref w);
            (w as CInventory).m_itemsSize = m_itemsSize;
            (w as CInventory).m_interval = m_interval;
            (w as CInventory).m_nbByLine = m_nbByLine;
            (w as CInventory).m_nbItems = m_nbItems;
            (w as CInventory).m_allowedTypes.Clear();
            foreach (int type in m_allowedTypes)
                (w as CInventory).m_allowedTypes.Add(type);
            (w as CInventory).FullInventoryEventHandler = FullInventoryEventHandler;
            (w as CInventory).m_lines.Clear();
            foreach (CContainerLine line in m_lines)
                (w as CInventory).m_lines.Add(line.Clone() as CContainerLine);
            (w as CInventory).GenerateWidget();
            //            (w as CVignette).IsProduceer = false;
            return w;
        }

        #endregion

        #region events

        /// <summary>
        /// Method called whenever the mouse is released and is over this widget and was pressed over this widget
        /// </summary>
        public override bool onRelease(CMouse mouse)
        {
            // Replace this by an HUD button that clean the inventory.
            foreach (DARE.CMouse.EButton evt in mouse.ButtonReleasedEvent)
                if (evt == CMouse.EButton.MIDDLE)
                    GenerateWidget();
            return base.onRelease(mouse);
        }

        /// <summary>
        /// Method called whenever the mouse is pressed and is over this widget
        /// </summary>
        public override bool onClick(CMouse mouse)
        {
            return base.onClick(mouse);
        }

        #endregion

        #region methods

        /// <summary>
        /// Gets the list of the CContainerLine contained by this inventory.
        /// </summary>
        public List<CContainerLine> GetLines()
        {
            List<CContainerLine> ret = new List<CContainerLine>();
            foreach (CContainerLine cl in m_lines)
                ret.Add(cl);
            return ret;
        }

        public CVignette TakeItem(CVignette vig)
        {
            foreach (CContainerLine line in m_lines)
                if (line.GetAllItems().Contains(vig))
                    return line.TakeItem(vig);
            return null;
        }

        /// <summary>
        /// Gets the list of each CVignette contained by this inventory
        /// </summary>
        public List<CVignette> GetAllItems()
        {
            List<CVignette> vig = new List<CVignette>();
            foreach (CContainerLine line in m_lines)
                vig.AddRange(line.GetAllItems());
            return vig;
        }

        /// <summary>
        /// Gets the list of IDs of the CVignettes contained by this inventory
        /// </summary>
        public List<int> GetAllItemID()
        {
            List<int> vig = new List<int>();
            foreach (CContainerLine line in m_lines)
                vig.AddRange(line.GetAllItemID());
            return vig;
        }

        /// <summary>
        /// Checks if the type identifier given in parameters is allowed
        /// </summary>
        /// <param name="type">the identifier of the type of item</param>
        public bool IsTypeAllowed(long type)
        {
            return m_allowedTypes.Contains(type);
        }

        /// <summary>
        /// Actualizes the widget.
        /// If anything has changed, it will allow this widget to get back its parameters.
        /// For example, if the size of the widget is a percentage of the size of its parent,
        /// if the parent's size changes, it will recalculate the good size of this widget.
        /// (This method is called automatically but you can force a new actualization).
        /// </summary>
        public override void Actualize()
        {
            Position = m_position;//Don't worry, it's normal.
            m_texture.Rectangle = m_rect;
            foreach (CWidget child in m_children.Values)
                child.Actualize();
        }

        private bool TryAttach(ref bool isAttached, CWidget from, CWidget to, ref bool breakLoop)
        {
            breakLoop = false;
            CVignette vigDest = to as CVignette;
            CVignette vigSrc = from as CVignette;
            if (vigDest != null && vigDest.IsCumulable)
            {
                if (vigDest.Cumulated + vigSrc.Cumulated <= vigDest.CumulLimit)
                {
                    vigDest.Cumulated += vigSrc.Cumulated;
                    vigSrc.Cumulated = 0;
                    vigDest.BottomRightText.Text = vigDest.Cumulated.ToString();
                    vigSrc.LastAttachedTo = null;
                    isAttached = true;
                    return false;
                }
                else if (vigDest.Cumulated < vigDest.CumulLimit)
                {
                    vigSrc.Cumulated -= vigDest.CumulLimit - vigDest.Cumulated;
                    vigDest.Cumulated = vigDest.CumulLimit;
                    vigSrc.BottomRightText.Text = vigSrc.Cumulated.ToString();
                    vigDest.BottomRightText.Text = vigDest.Cumulated.ToString();
                    isAttached = Attach(vigSrc);
                    breakLoop = true;
                }
            }
            return true;
        }

        /// <summary>
        /// This attachs a containable widget to a container in a widget that is a container group (`IsContainerGroup = true`)
        /// </summary>
        /// <param name="from">This is the widget to be attached</param>
        /// <param name="to">Can be a cumulable widget to which we want to attach the "from" widget. For example, if you drop a Vignette on another representing the same item, and if this vignette is cumulable, set the give the vignette on which you will attach the first one.</param>
        public override bool Attach(CWidget from, CWidget to)//This method can be cleaned a lot.
        {
            if (from == null)
                return false;
            bool isAttached = false;
            bool breakLoop = false;
            if (to != null)
            {
                bool ret = TryAttach(ref isAttached, from, to, ref breakLoop);
                if (!ret)
                    return false;
            }
            if (!isAttached)
                foreach (CContainerLine line in m_lines)
                {
                    for (int i = 0; i < line.NbByLine; ++i)
                        if (line.elementAt(i).Children.Count > 0 && (line.elementAt(i).Children.First().Value as CVignette).ID == (from as CVignette).ID)
                        {
                            bool ret = TryAttach(ref isAttached, from, line.elementAt(i).Children.First().Value, ref breakLoop);
                            if (!ret)
                                return false;
                            if (breakLoop)
                                break;
                        }
                        else if (line.elementAt(i).Children.Count == 0 && line.elementAt(i).Equals((from as CVignette).LastAttachedTo))
                            return false;
                }

            foreach (CContainerLine line in m_lines)
            {
                if (isAttached)
                    break;
                for (int i = 0; i < line.NbByLine; ++i)
                {
                    if (line.elementAt(i).Children.Count == 0)
                    {
                        if (HUD.Widgets.ContainsValue(from))
                            HUD.Detach(from.Name);
                        from.Parent = line.elementAt(i);
                        if (from.Parent.Children.ContainsKey(from.Name))
                            from.Parent.Children.Remove(from.Name);
                        from.Parent.Children.Add(from.Name, from);
                        from.Actualize();
                        isAttached = true;
                        break;
                    }
                }
            }
            if (!isAttached)
            {
                bool destroy = true;
                destroy = FullInventoryEventHandler.Invoke(this, null);
                if (!destroy)
                    return false;
                if (!HUD.Widgets.ContainsValue(from))
                    HUD.Add(from);
                from.Hide();
            }
            return isAttached;
        }

        public override bool Attach(CWidget from)
        {
            return Attach(from, null);
        }

        private void GenerateWidget()
        {
            int j = 0;
            int lines = m_nbItems / m_nbByLine;
            int last = m_nbItems % m_nbByLine;
            int totalLines = lines + (last == 0 ? 0 : 1);
            if (m_nbByLine > m_nbItems)
                m_nbByLine = m_nbItems;
            else if (m_nbItems != m_nbByLine && last == 0)
                last = m_nbByLine;
            m_size = new Vector2((m_nbByLine * (m_itemsSize.X + m_interval)) + m_margins.Left + m_margins.Right, (totalLines * (m_itemsSize.Y + m_interval)) + m_margins.Top + m_margins.Bottom);
            List<CVignette> vignettes = new List<CVignette>();
            foreach (CContainerLine line in m_lines)
            {
                for (int i = 0; i < line.NbByLine; ++i)
                    if (line.elementAt(i).Children.Count > 0)
                        vignettes.Add(line.elementAt(i).Children.First().Value as CVignette);
            }
            foreach (CWidget wid in m_lines)
                HUD.Remove(wid);
            m_lines.Clear();
            foreach (CWidget wid in m_children.Values)
                HUD.Remove(wid);
            m_children.Clear();
            for (int i = m_lines.Count; i < totalLines; ++i)
                {
                    m_lines.Add(new CContainerLine(HUD, this, m_name + "_line_" + m_lines.Count));
                    if (totalLines != 1 && i == totalLines - 1)
                        m_lines[i].NbByLine = last;
                    else
                        m_lines[i].NbByLine = m_nbByLine;
                    m_lines[i].ItemsSize = m_itemsSize;
                    m_lines[i].Interval = m_interval;
                    m_lines[i].Size = new Vector2(1, 1.0f / totalLines);
                    m_lines[i].Position = new Vector2(m_margins.Left, m_margins.Top + (i * (m_lines[i].ItemsSize.Y + m_lines[i].Interval)));
                    m_lines[i].AllowedTypes = m_allowedTypes;
                }
            Actualize();
            foreach (CVignette vig in vignettes)
                this.Attach(vig);
        }

        #endregion

    }
}
